 /*******************************************************************************
  * Copyright (c) 2004, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/

 package org.eclipse.ui.internal.intro.impl.model;

 import java.util.ArrayList ;
 import java.util.Map ;
 import java.util.StringTokenizer ;
 import java.util.Vector ;

 import org.eclipse.core.runtime.IConfigurationElement;
 import org.eclipse.core.runtime.IRegistryChangeEvent;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.swt.SWTError;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.ui.IMemento;
 import org.eclipse.ui.internal.intro.impl.model.loader.ModelLoaderUtil;
 import org.eclipse.ui.internal.intro.impl.model.util.ModelUtil;
 import org.eclipse.ui.internal.intro.impl.presentations.BrowserIntroPartImplementation;
 import org.eclipse.ui.internal.intro.impl.presentations.FormIntroPartImplementation;
 import org.eclipse.ui.internal.intro.impl.presentations.TextIntroPartImplementation;
 import org.eclipse.ui.internal.intro.impl.util.Log;
 import org.eclipse.ui.internal.intro.impl.util.StringUtil;
 import org.eclipse.ui.intro.IIntroPart;

 /**
  * This class models the presentation element contributed to a config extension point. The
  * Presentation class delegates UI creation to the actual Implementation class, and passes the
  * IntroPart along to this implementation class. Also, dynamic awarness is honored here.
  * <p>
  * Rules:
  * <ul>
  * <li>There is no model class for the "implementation" markup element. This presentation class
  * inherits information from the implementation class that is picked (based on OS, ...).</li>
  * <li>ID attribute of this model class is the id of the picked implementation element.</li>
  * <li>Style attribute in this model class is the style of the picked implementation element.</li>
  * <li>HTMLHeadContent in this model class is the HEAD element under the picked implementation
  * element, only if the implementation element is a Browser implmenetation.</li>
  * <li>The UI model class, AbstractIntroPartImplementation, that represents the IntroPart
  * implementation is cached here for quick access. It is used by intro url actions for manipulation
  * of UI.<br>
  * INTRO:This really should be in a UI model class.
  * <ul>
  */
 public class IntroPartPresentation extends AbstractIntroElement {

     protected static final String TAG_PRESENTATION = "presentation"; //$NON-NLS-1$
 private static final String TAG_IMPLEMENTATION = "implementation"; //$NON-NLS-1$

     private static final String ATT_KIND = "kind"; //$NON-NLS-1$
 private static final String ATT_STYLE = "style"; //$NON-NLS-1$
 private static final String ATT_OS = "os"; //$NON-NLS-1$
 private static final String ATT_WS = "ws"; //$NON-NLS-1$
 protected static final String ATT_HOME_PAGE_ID = "home-page-id"; //$NON-NLS-1$
 protected static final String ATT_STANDBY_PAGE_ID = "standby-page-id"; //$NON-NLS-1$

     public static final String BROWSER_IMPL_KIND = "html"; //$NON-NLS-1$
 public static final String FORMS_IMPL_KIND = "swt"; //$NON-NLS-1$
 // this implementation kind if not public api. Only used internally for
 // debugging.
 private static final String TEXT_IMPL_KIND = "text"; //$NON-NLS-1$


     // private String title;
 private String [] implementationStyles;
     private String implementationKind;
     private String homePageId;
     private String standbyPageId;

     // The Head contributions to this preentation (inherited from child
 // implementation).
 private IntroHead head;

     private AbstractIntroPartImplementation implementation;

     private IntroLaunchBarElement launchBar;

     // CustomizableIntroPart and memento instances. Passed to the Implementation
 // classes.
 private IIntroPart introPart;
     private IMemento memento;

     /**
      *
      */
     IntroPartPresentation(IConfigurationElement element) {
         super(element);
         homePageId = element.getAttribute(ATT_HOME_PAGE_ID);
         standbyPageId = element.getAttribute(ATT_STANDBY_PAGE_ID);
     }

     private void updatePresentationAttributes(IConfigurationElement element) {
         if (element != null) {
             // reset (ie: inherit) type and style to be implementation type and
 // style. Then handle HEAD content for the case of HTML Browser.
 String value = element.getAttribute(ATT_STYLE);
             if (value!=null) {
                 IntroModelRoot root = getModelRoot();
                 ArrayList list = new ArrayList ();
                 StringTokenizer stok = new StringTokenizer (value, ","); //$NON-NLS-1$
 for (;stok.hasMoreTokens();) {
                     String oneStyle = stok.nextToken().trim();
                     if (root!=null)
                         oneStyle = root.resolveVariables(oneStyle);
                     list.add(oneStyle);
                 }
                 implementationStyles = (String [])list.toArray(new String [list.size()]);
             }
             implementationKind = element.getAttribute(ATT_KIND);
             // get Head contribution, regardless of implementation class.
 // Implementation class is created lazily by UI.
 head = getHead(element);
             // Resolve.
 if (implementationStyles!=null) {
                 for (int i=0; i<implementationStyles.length; i++) {
                     implementationStyles[i] = ModelUtil.resolveURL(implementationStyles[i], element);
                 }
             }
         }
     }

     /**
      * Returns the styles associated with the Presentation. May be null if no shared presentation
      * style is needed, or in the case of static HTML OOBE.
      *
      * @return Returns the array of styles or null if not defined.
      */
     public String [] getImplementationStyles() {
         return implementationStyles;
     }

     /**
      * Returns the type attribute of the implementation picked by this presentation.
      *
      * @return Returns the implementationKind.
      */
     public String getImplementationKind() {
         return implementationKind;
     }

     public AbstractIntroPartImplementation getIntroPartImplementation() {
         return implementation;
     }


     /**
      * Returns the model class for the Head element under an implementation. Returns null if there
      * is no head contribution.
      *
      * @param element
      * @return
      */
     private IntroHead getHead(IConfigurationElement element) {
         try {
             // There should only be one head element. Since elements where
 // obtained by name, no point validating name.
 IConfigurationElement[] headElements = element.getChildren(IntroHead.TAG_HEAD);
             if (headElements.length == 0)
                 // no contributions. done.
 return null;
             IntroHead head = new IntroHead(headElements[0]);
             head.setParent(this);
             return head;
         } catch (Exception e) {
             Log.error(e.getMessage(), e);
             return null;
         }
     }

     /**
      * Returns the launch bar element if defined in this presentation, or <code>null</code>
      * otherwise.
      *
      * @since 3.1
      * @return
      */

     public IntroLaunchBarElement getLaunchBarElement() {
         if (launchBar != null)
             return launchBar;
         IConfigurationElement[] children = getCfgElement().getChildren("launchBar"); //$NON-NLS-1$
 if (children.length > 0) {
             launchBar = new IntroLaunchBarElement(children[0]);
             launchBar.setParent(this);
             if (children.length > 1)
                 Log
                         .warning("Mutiple Intro Launch bars defined when only one is allowed. Only first one was loaded. "); //$NON-NLS-1$
 }
         return launchBar;
     }

     /**
      * @param introPart
      */
     public void init(IIntroPart introPart, IMemento memento) {
         // REVISIT: Called when the actual UI needs to be created. Incomplete
 // separation of model / UI. Will change later. should not get here if
 // there is no valid implementation.
 this.introPart = introPart;
         this.memento = memento;
     }

     /**
      * Creates the UI based on the implementation class.
      *
      * @see org.eclipse.ui.IWorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
      */
     public void createPartControl(Composite parent) {
         Vector validImplementations = getValidImplementationElements(getCfgElement());
         IConfigurationElement implementationElement = null;
         for (int i = 0; i < validImplementations.size(); i++) {
             implementationElement = (IConfigurationElement) validImplementations.elementAt(i);
             // you want to pass primed model.
 updatePresentationAttributes(implementationElement);
             try {
                 implementation = createIntroPartImplementation(getImplementationKind());
                 if (implementation == null)
                     // failed to create executable.
 continue;

                 implementation.init(introPart, memento);
                 implementation.createPartControl(parent);
                 IntroModelRoot model = getModelRoot();
                 if (model != null && model.getConfigurer() != null) {
                     IntroTheme theme = model.getTheme();
                     Map properties = theme!=null?theme.getProperties():null;
                     model.getConfigurer().init(introPart.getIntroSite(), properties);
                 }
                 if (Log.logInfo)
                     Log.info("Loading Intro UI implementation from: " //$NON-NLS-1$
 + ModelLoaderUtil.getLogString(implementationElement, "kind")); //$NON-NLS-1$
 break;
             } catch (SWTError e) {
                 Log.warning("Failed to create Intro UI implementation from: " //$NON-NLS-1$
 + ModelLoaderUtil.getLogString(implementationElement, "kind") + e.getMessage()); //$NON-NLS-1$
 implementation = null;
                 implementationElement = null;
             } catch (Exception e) {
                 Log.error("Failed to create Intro UI implementation from: " //$NON-NLS-1$
 + ModelLoaderUtil.getLogString(implementationElement, "kind"), e); //$NON-NLS-1$
 implementation = null;
                 implementationElement = null;
             }
         }

         if (implementationElement == null) {
             // worst case scenario. We failed in all cases.
 implementation = new FormIntroPartImplementation();
             try {
                 implementation.init(introPart, memento);
                 // simply set the presentation kind since all other attributes
 // will be null.
 implementationKind = FORMS_IMPL_KIND;
             } catch (Exception e) {
                 // should never be here.
 Log.error(e.getMessage(), e);
                 return;
             }
             implementation.createPartControl(parent);
             Log.warning("Loaded UI Forms implementation as a default UI implementation."); //$NON-NLS-1$
 }
     }

     /**
      * Retruns a list of valid implementation elements of the config. Choose correct implementation
      * element based on os atrributes. Rules: get current OS, choose first contributrion, with os
      * that matches OS. Otherwise, choose first contribution with no os. Returns null if no valid
      * implementation is found.
      */
     private Vector getValidImplementationElements(IConfigurationElement configElement) {

         Vector validList = new Vector ();

         // There can be more than one implementation contribution. Add each
 // valid one. First start with OS, then WS then no OS.
 IConfigurationElement[] implementationElements = configElement.getChildren(TAG_IMPLEMENTATION);
         // IConfigurationElement implementationElement = null;

         if (implementationElements.length == 0)
             // no contributions. done.
 return validList;

         String currentOS = Platform.getOS();
         String currentWS = Platform.getWS();

         // first loop through all to find one with matching OS, with or
 // without WS.
 for (int i = 0; i < implementationElements.length; i++) {
             String os = implementationElements[i].getAttribute(ATT_OS);
             if (os == null)
                 // no os, no match.
 continue;

             if (listValueHasValue(os, currentOS)) {
                 // found implementation with correct OS. Now try if WS
 // matches.
 String ws = implementationElements[i].getAttribute(ATT_WS);
                 if (ws == null) {
                     // good OS, and they do not care about WS. we have a
 // match.
 validList.add(implementationElements[i]);
                 } else {
                     // good OS, and we have WS.
 if (listValueHasValue(ws, currentWS))
                         validList.add(implementationElements[i]);
                 }
             }
         }

         // now loop through all to find one with no OS defined, but with a
 // matching WS.
 for (int i = 0; i < implementationElements.length; i++) {
             String os = implementationElements[i].getAttribute(ATT_OS);
             if (os == null) {
                 // found implementation with no OS. Now try if WS
 // matches.
 String ws = implementationElements[i].getAttribute(ATT_WS);
                 if (ws == null) {
                     // no OS, and they do not care about WS. we have a
 // match.
 validList.add(implementationElements[i]);
                 } else {
                     // no OS, and we have WS.
 if (listValueHasValue(ws, currentWS))
                         validList.add(implementationElements[i]);
                 }

             }
         }

         return validList;

     }

     /**
      * Util method that searches for the given value in a comma separated list of values. The list
      * is retrieved as an attribute value of OS, WS.
      *
      */
     private boolean listValueHasValue(String stringValue, String value) {
         String [] attributeValues = StringUtil.split(stringValue, ","); //$NON-NLS-1$
 for (int i = 0; i < attributeValues.length; i++) {
             if (attributeValues[i].equalsIgnoreCase(value))
                 return true;
         }
         return false;
     }

     /**
      * Util method to load shared style from given kind.
      */
     public String getSharedStyle(String kind) {
         // There can be more than one implementation contribution.
 IConfigurationElement[] implementationElements = getCfgElement().getChildren(TAG_IMPLEMENTATION);
         // IConfigurationElement implementationElement = null;

         if (implementationElements.length == 0)
             // no implementations. done.
 return null;

         // loop through all to find one with matching kind.
 for (int i = 0; i < implementationElements.length; i++) {
             String aKind = implementationElements[i].getAttribute(ATT_KIND);
             if (aKind.equals(kind)) {
                 // found implementation with matching kind.
 String style = implementationElements[i].getAttribute(ATT_STYLE);
                 return ModelUtil.resolveURL(style, getCfgElement());
             }
         }
         return null;
     }


     /**
      * Creates the actual implementation class. Returns null on failure.
      *
      */
     private AbstractIntroPartImplementation createIntroPartImplementation(String implementationType) {
         // quick exits
 if (implementationType == null)
             return null;
         if (!implementationType.equals(BROWSER_IMPL_KIND) && !implementationType.equals(FORMS_IMPL_KIND)
                 && !implementationType.equals(TEXT_IMPL_KIND))
             return null;

         AbstractIntroPartImplementation implementation = null;
         try {
             if (implementationType.equals(BROWSER_IMPL_KIND))
                 implementation = new BrowserIntroPartImplementation();
             else if (implementationType.equals(FORMS_IMPL_KIND))
                 implementation = new FormIntroPartImplementation();
             else
                 implementation = new TextIntroPartImplementation();
         } catch (Exception e) {
             Log.error("Could not instantiate implementation " //$NON-NLS-1$
 + implementationType, e);
         }
         return implementation;
     }

     /**
      * Returns the the Customizable Intro Part. may return null if init() has not been called yet on
      * the presentation.
      *
      * @return Returns the introPart.
      */
     public IIntroPart getIntroPart() {
         return introPart;
     }

     /**
      * Save the current state of the intro. Delegate to the implementation to do the work, as
      * different implementations may have different requirements.
      *
      * @param memento
      * the memento in which to store state information
      */
     public void saveState(IMemento memento) {
         if (implementation != null)
             implementation.saveState(memento);
     }


     public void setFocus() {
         if (implementation != null)
             implementation.setFocus();
     }

     public void standbyStateChanged(boolean standby, boolean isStandbyPartNeeded) {
         if (implementation != null)
             implementation.standbyStateChanged(standby, isStandbyPartNeeded);
     }

     public void updateHistory(AbstractIntroPage page) {
         if (implementation != null)
             implementation.updateHistory(page);
     }



     public boolean navigateForward() {
         if (implementation != null)
             return implementation.navigateForward();
         return false;
     }

     public boolean navigateBackward() {
         if (implementation != null)
             return implementation.navigateBackward();
         return false;
     }

     public boolean navigateHome() {
         if (implementation != null)
             return implementation.navigateHome();
         return false;
     }


     /**
      * Called when the IntroPart is disposed. Forwards the call to the implementation class.
      */
     public void dispose() {
         if (implementation != null)
             implementation.dispose();
     }

     /**
      * Support dynamic awarness. Clear cached models first, then update UI by delegating to
      * implementation.
      *
      * @see org.eclipse.core.runtime.IRegistryChangeListener#registryChanged(org.eclipse.core.runtime.IRegistryChangeEvent)
      */
     public void registryChanged(IRegistryChangeEvent event) {
         if (implementation != null)
             implementation.registryChanged(event);
     }

     /**
      * @return Returns the homePageId.
      */
     public String getHomePageId() {
         return homePageId;
     }

     /**
      * @return Returns the homePageId.
      */
     public String getStandbyPageId() {
         return standbyPageId;
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.ui.internal.intro.impl.model.IntroElement#getType()
      */
     public int getType() {
         return AbstractIntroElement.PRESENTATION;
     }

     /**
      * @return Returns the HTML head conttent to be added to each dynamic html page in this
      * presentation..
      */
     public IntroHead getHead() {
         return head;
     }




 }

